#include "FusionConvert.h"
#include "common/system_log.h"

#include "utils/SystemUtils.h"
#include "utils/file_util.h"
#include "utils/string_util.h"
#include "ExchangeIO.h"
#include <memory>
#include <unordered_map>

#ifndef CXX_LESS_17
#include <filesystem>
#else
#include <cstdio>
#endif

#include <iostream>

#ifdef __linux__
#include <unistd.h> 
#include <fcntl.h>
#endif

#ifdef INTEROP_ENABLE
#include "InterOpIO.h"
#include "DataKitIO.h"
#include "ExchangeParameter.h"
#include "ExchangeInterOp.h"
#endif
#include "ExchangeInterOp.h"

AMCAX_EXCHANGE_NAMESPACE_BEGIN

#ifdef INTEROP_ENABLE

enum class UsingCompontent{
    NONE,
    INTEROP,
    DATAKIT,
    AMCAX
} ;

static std::unordered_map<std::string, UsingCompontent> USE_WRITER_BY_INPUT_FILE_EXTENSION_NAME_TYPE_MAP =
{
    {".gltf",UsingCompontent::DATAKIT},
    {".glb",UsingCompontent::DATAKIT},
    {".obj",UsingCompontent::DATAKIT},
    {".stl",UsingCompontent::DATAKIT},
    {".fbx",UsingCompontent::DATAKIT}
} ;

static std::unordered_map<std::string, UsingCompontent> USE_WRITER_BY_FILE_EXTENSION_NAME_TYPE_MAP =
{
    {".iam",UsingCompontent::INTEROP},
    {".ipt",UsingCompontent::INTEROP},
    {".catpart",UsingCompontent::INTEROP},
    {".catproduct",UsingCompontent::INTEROP},
    {".stl",UsingCompontent::INTEROP},
    {".step",UsingCompontent::INTEROP},
    {".stp",UsingCompontent::INTEROP},
    {".iges",UsingCompontent::INTEROP},
    {".igs",UsingCompontent::INTEROP},
    {".obj",UsingCompontent::AMCAX},
    {".amx",UsingCompontent::AMCAX},
    {".x_t",UsingCompontent::DATAKIT},
    {".x_b",UsingCompontent::DATAKIT},
    {".sldprt",UsingCompontent::DATAKIT},
    {".sldasm",UsingCompontent::DATAKIT},
} ;

UsingCompontent GetWriterByExtensionWithDot(const std::string& extensionWithDot)
{
    // LOG_INFO("extension_with_dot:{}",extension_with_dot) ;
    auto it = USE_WRITER_BY_FILE_EXTENSION_NAME_TYPE_MAP.find(extensionWithDot) ;
    if (it == USE_WRITER_BY_FILE_EXTENSION_NAME_TYPE_MAP.end()) {
        return UsingCompontent::NONE ;
    }
    return it->second ;
} ;

static std::unordered_map<std::string , std::vector<UsingCompontent>> USE_READER_BY_FILE_EXTENSION_NAME_TYPE_MAP = 
{
    {".step",{UsingCompontent::INTEROP,UsingCompontent::DATAKIT}},
    {".stp",{UsingCompontent::INTEROP,UsingCompontent::DATAKIT}},
    {".stl",{UsingCompontent::DATAKIT}},
    {".obj",{UsingCompontent::DATAKIT}},
    {".gltf",{UsingCompontent::DATAKIT}},
    {".glb",{UsingCompontent::DATAKIT}},
    {".fbx",{UsingCompontent::DATAKIT}}
} ;

std::vector<UsingCompontent> GetReaderByExtensionWithDot(const std::string& extensionWithDot)
{
    // LOG_INFO("extension_with_dot:{}",extension_with_dot) ;
    auto it = USE_READER_BY_FILE_EXTENSION_NAME_TYPE_MAP.find(extensionWithDot) ;
    if (it == USE_READER_BY_FILE_EXTENSION_NAME_TYPE_MAP.end()) {
        return {} ;
    }
    return it->second ;
} ;


void FusionConvert(const std::string &inFilePath, const std::string &outFilePath)
{   
    
    std::string outFileExtensionWithDot = file_util::get_extension(outFilePath) ;
    auto exchange_type = GetWriterByExtensionWithDot(outFileExtensionWithDot) ;

    std::string inFileExtensionWithDot = file_util::get_extension(inFilePath) ;
    if (USE_WRITER_BY_INPUT_FILE_EXTENSION_NAME_TYPE_MAP.find(inFileExtensionWithDot) != USE_WRITER_BY_INPUT_FILE_EXTENSION_NAME_TYPE_MAP.end())
    {
        exchange_type = USE_WRITER_BY_INPUT_FILE_EXTENSION_NAME_TYPE_MAP[inFileExtensionWithDot];
    }

    // LOG_INFO("inFilePath:{} outFilePath:{}",inFilePath,outFilePath) ;
    switch (exchange_type) {
        case UsingCompontent::INTEROP:
            LOG_DEBUG("ConvertUsingInterOp") ;
            ConvertUsingInterOp(inFilePath,outFilePath, nullptr, nullptr) ;
            break;
        case UsingCompontent::DATAKIT:
            LOG_DEBUG("ConvertUsingDataKit") ;
            ConvertUsingDataKit(inFilePath,outFilePath, nullptr, nullptr) ;
            break;
        case UsingCompontent::AMCAX:
            LOG_DEBUG("ConvertUsingAMCAX") ;  
            ConvertUsingAMCAX(inFilePath,outFilePath, nullptr, nullptr) ;
            break;
        default:
            LOG_ERROR("unsupport file type") ;
            break;
    
    }

    #ifdef __linux__
        if(!string_util::IsEqualIgnoreCaseAfterTrim(".amx", outFileExtensionWithDot)){
            NewProcessToAMCAX(inFilePath,outFilePath) ;
        }
    #endif 
}

ExchangeParameter *GetMetaData(const std::string &inFilePath, const std::string &outFilePath,
                               const std::shared_ptr<ExchangeParameter> &inputParameter)
{
    ExchangeParameter *resultMetaDataInfo = nullptr;
#ifdef INTEROP_ENABLE
    resultMetaDataInfo = GetMetaDataByInterOp(inFilePath, outFilePath, inputParameter);
#endif
    return resultMetaDataInfo;
}

ExchangeParameter *GetMetaDataByInterOp(const std::string &inFilePath, const std::string &outFilePath,
                                        const std::shared_ptr<ExchangeParameter> &inputParameter)
{
    if(InterOp::ExchangeInterOp::CheckLicense())
    {
        LOG_INFO("InterOp license check success") ;
    }
    else
    {
        LOG_ERROR("InterOp license check failed") ;
        return nullptr;
    }

    AMCAX::Exchange::InterOp::FacetOptions facetOptions;
    auto interOpInputParameter = std::dynamic_pointer_cast<InterOpInputParameter>(inputParameter);
    if(interOpInputParameter)
    {
        facetOptions.maxEdgeLength = interOpInputParameter->GetMaxEdgeLength();
        interOpInputParameter->GetFacetType() == INTEROP_META_FACET_TYPE::FACET_TYPE_TRIANGLE ? 
            facetOptions.facetType = AMCAX::Exchange::InterOp::FacetType::FACET_TYPE_TRIANGLE : 
            facetOptions.facetType = AMCAX::Exchange::InterOp::FacetType::FACET_TYPE_QUAD;
    }
    // facetOptions.facetType = AMCAX::Exchange::InterOp::FacetType::FACET_TYPE_TRIANGLE;
    AMCAX::Exchange::InterOp::MetaDataInfo resultInfo = AMCAX::Exchange::InterOp::IO::GetInstance().GetMetaData(inFilePath,outFilePath, facetOptions);
    std::list<InterOpResultParameter::Point3D> vertexes;
    std::transform(resultInfo.vertexes.begin(), resultInfo.vertexes.end(), std::back_inserter(vertexes),
        [](const AMCAX::Exchange::InterOp::Point3D &point) {
            return InterOpResultParameter::Point3D{point.x, point.y, point.z};
        });
    std::list<std::list<InterOpResultParameter::Point3D>> triangles;
    for (const auto &triangle : resultInfo.triangles) {
        std::list<InterOpResultParameter::Point3D> resultTriangle;
        std::transform(triangle.begin(), triangle.end(), std::back_inserter(resultTriangle),
            [](const AMCAX::Exchange::InterOp::Point3D &point) {
                return InterOpResultParameter::Point3D{point.x, point.y, point.z};
            });
        triangles.push_back(resultTriangle);
    }

    auto interOpResultParameter = new InterOpResultParameter();
    interOpResultParameter->SetVolume(resultInfo.volume);         
    interOpResultParameter->SetArea(resultInfo.area);
    interOpResultParameter->SetVertexes(vertexes);
    interOpResultParameter->SetTriangles(triangles);
    interOpResultParameter->SetTopologyJsonFilePath(resultInfo.topologyJsonFilePath);
    return interOpResultParameter;
}

void ConvertUsingInterOp(const std::string &inFilePath, const std::string &outFilePath,
    const std::shared_ptr<ExchangeParameter> &inputParameter,
    ExchangeParameter* resultParameter)
{
    // InterOp::IO::GetInstance().Convert(inFilePath,outFilePath) ;
    if(InterOp::ExchangeInterOp::CheckLicense())
    {
        LOG_INFO("InterOp license check success") ;
    }
    else
    {
        LOG_ERROR("InterOp license check failed") ;
        return ;
    }

    AMCAX::Exchange::InterOp::IO::GetInstance().Convert(inFilePath,outFilePath);
}

void ConvertUsingDataKit(const std::string &inFilePath, const std::string &outFilePath,
    const std::shared_ptr<ExchangeParameter> &inputParameter,
    ExchangeParameter* resultParameter)
{
    std::string inFileExtensionWithDot = file_util::get_extension(inFilePath) ;
    auto readers = GetReaderByExtensionWithDot(inFileExtensionWithDot) ;
    std::string tempFilePath ;
    if (std::find(readers.begin(), readers.end(),UsingCompontent::DATAKIT) != readers.end())
    {
        /* code */
        tempFilePath = inFilePath ;
    }else{
        tempFilePath = outFilePath + ".step";
        ConvertUsingInterOp(inFilePath, tempFilePath, inputParameter, resultParameter);
    }

    
    if(!file_util::is_file_exist(tempFilePath))
    {
        return;
    }
    LOG_INFO("tempFilePath : {}",tempFilePath) ;
    AMCAX::Exchange::DataKit::IO::GetInstance().Initialize();
    AMCAX::Exchange::DataKit::IO::GetInstance().Convert(tempFilePath, outFilePath);
    AMCAX::Exchange::DataKit::IO::GetInstance().Terminate();
    #ifndef CXX_LESS_17
    // std::filesystem::remove(tempFilePath);
    #else
    // std::remove(tempFilePath.c_str()) ;
    #endif
}

void ConvertUsingAMCAX(const std::string &inFilePath, const std::string &outFilePath,
    const std::shared_ptr<ExchangeParameter> &inputParameter,
    ExchangeParameter* resultParameter)
{
    // LOG_INFO("inFilePath:{} outFilePath:{}",inFilePath,outFilePath) ;
    std::string outFileExtensionWithDot = file_util::get_extension(inFilePath) ;
    auto file_type = GetWriterByExtensionWithDot(outFileExtensionWithDot) ;
    if (UsingCompontent::NONE == file_type)
    {
        LOG_ERROR("unsupport file type: {}", outFileExtensionWithDot) ;
        return ;
    }
    
    
    std::shared_ptr<AMCAX::Exchange::ExchangeIO> readIo = std::make_shared<AMCAX::Exchange::InterOp::ExchangeInterOp>(inFilePath) ;
    if (nullptr == readIo) {
        LOG_ERROR("Invalid input file:{}",inFilePath) ;
        return  ;
    }
    readIo->Read() ;

    // if (readIo->GetShapes().empty()) {
    //     LOG_ERROR("Invalid input file:{}",inFilePath) ;
    //     return ;
    // }
    if(!readIo->GetDocumentRootInstance())
    {
        LOG_ERROR("Invalid input file:{}",inFilePath);
        LOG_ERROR("No valid solid body to output to target file:{}",outFilePath);
        return;
    }
    
    std::shared_ptr<AMCAX::Exchange::ExchangeIO> writeIo = AMCAX::Exchange::Create(outFilePath,true) ;
    writeIo->Write(readIo->GetShapes(), readIo->GetDocumentRootInstance());
}

void NewProcessToAMCAX(const std::string &inFilePath,const std::string &outFilePath){

#ifdef __linux__
    std::string middleProcessOn = SystemUtil::GetEnv("AMCAX_ON") ;
    
    if (!string_util::IsEqualIgnoreCaseAfterTrim(middleProcessOn,"on"))
    {
        return ;
    }
    
    std::string fileName = file_util::get_filename_without_extension(outFilePath) ;
    #ifndef CXX_LESS_17
    std::filesystem::path parentPath = file_util::GetParentPath(outFilePath) ;
    std::string amxFilePath = parentPath / (fileName + ".amx") ;
    std::string currentExeDir = file_util::GetExecutableDir() ;
    std::string currentExePath = std::filesystem::path(currentExeDir) / "ExchangeHelper" ;
    #else
    std::string parentPath = file_util::GetParentPath(outFilePath) ;
    std::string amxFilePath = parentPath + "/" +  (fileName + ".amx") ;
    std::string currentExeDir = file_util::GetExecutableDir() ;
    std::string currentExePath = currentExeDir +  "/ExchangeHelper" ;
    #endif
    if (!file_util::is_file_exist(currentExePath)) {
        LOG_ERROR("{} not exist.",currentExeDir) ; 
        throw currentExePath + " not exist" ;
    }
    
    std::string originFilePath = inFilePath ;
    char *args[] = {&currentExePath[0],(char*)"-i",&originFilePath[0],"-o",&amxFilePath[0],nullptr} ;
    // Convert file to amx
    pid_t pid = fork();
    if (pid == 0) {
        execv(currentExePath.c_str(),args) ;    
        _exit(0) ;
    }
#endif
}

#endif

AMCAX_EXCHANGE_NAMESPACE_END
